package packing;

import geometric3D.*;

import it.unimi.dsi.fastutil.Stack;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
import it.unimi.dsi.fastutil.objects.ObjectSortedSet;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;

import Comparator.*;

public class State {

	@Override
	public String toString() {
		return "State [id=" + id + ", evaluationScore=" + evaluationScore + "]";
	}

	private String id;

	// private boolean [][][] markArray;
	private double evaluationScore;
	private Container container;
	private List<BoxInput> boxInputList;
	private List<EmptySpaceBlockPair> packedBlockList;
	private ObjectSortedSet<BoxPack> boxPackSet;
	private Stack<EmptySpace> emptySpaceStack;
	private boolean isValid = false;
	private ObjectRBTreeSet<XYRectangle> xyRectanglePositiveSet;
	private ObjectRBTreeSet<XYRectangle> xyRectangleNegativeSet;
	private ObjectRBTreeSet<XZRectangle> xzRectanglePositiveSet;
	private ObjectRBTreeSet<XZRectangle> xzRectangleNegativeSet;
	private ObjectRBTreeSet<YZRectangle> yzRectanglePositiveSet;
	private ObjectRBTreeSet<YZRectangle> yzRectangleNegativeSet;
	private int xMinSize;
	private int yMinSize;
	private int zMinSize;

	public Stack<EmptySpace> getEmptySpaceStack() {
		return emptySpaceStack;
	}

	public void setValid(boolean isValid) {
		this.isValid = isValid;
	}

	public int getQuantity(BoxPack box) {
		return boxInputList.get(box.getId() - 1).getQuantity();

	}

	public State copyNewState() {
		List<BoxInput> newBoxInputList = new ArrayList<BoxInput>();
		for (BoxInput boxInput : boxInputList) {
			newBoxInputList.add(boxInput.copy());

		}

		return new State(this.id, this.container, newBoxInputList);

	}

	public State() {
		super();
	}

	public State(String id, Container container, List<BoxInput> boxInputList) {
		super();
		this.id = id;
		this.container = container;
		this.boxInputList = boxInputList;

		evaluationScore = 0;
		boxPackSet = new ObjectRBTreeSet<BoxPack>();
		xyRectangleNegativeSet = new ObjectRBTreeSet<XYRectangle>(
				new XYRectangleComparator(true));
		xyRectanglePositiveSet = new ObjectRBTreeSet<XYRectangle>(
				new XYRectangleComparator(false));

		xzRectangleNegativeSet = new ObjectRBTreeSet<XZRectangle>(
				new XZRectangleComparator(true));
		xzRectanglePositiveSet = new ObjectRBTreeSet<XZRectangle>(
				new XZRectangleComparator(false));

		yzRectangleNegativeSet = new ObjectRBTreeSet<YZRectangle>(
				new YZRectangleComparator(true));
		yzRectanglePositiveSet = new ObjectRBTreeSet<YZRectangle>(
				new YZRectangleComparator(false));

		for (BoxInput boxInput : boxInputList) {
			boxPackSet.addAll(boxInput.getBoxPackList());

		}

		packedBlockList = new ObjectArrayList<EmptySpaceBlockPair>();
		emptySpaceStack = new ObjectArrayList<EmptySpace>();
		emptySpaceStack.push(new EmptySpace(new Point(0, 0, 0), container
				.getWidth(), container.getLength(), container.getHeight()));
		xyRectangleNegativeSet.add(container.getXYRectangle(true).reverse());
		xyRectanglePositiveSet.add(container.getXYRectangle(false).reverse());
		yzRectangleNegativeSet.add(container.getYZRectangle(true).reverse());
		yzRectanglePositiveSet.add(container.getYZRectangle(false).reverse());
		xzRectangleNegativeSet.add(container.getXZRectangle(true).reverse());
		xzRectanglePositiveSet.add(container.getXZRectangle(false).reverse());

	}

	public boolean insideContainer(EmptySpaceBlockPair emptySpaceBlockPair) {
		if (emptySpaceBlockPair.getMinX() < 0
				|| emptySpaceBlockPair.getMaxX() > container.getWidth()
				|| emptySpaceBlockPair.getMinY() < 0
				|| emptySpaceBlockPair.getMaxY() > container.getLength()
				|| emptySpaceBlockPair.getMinZ() < 0
				|| emptySpaceBlockPair.getMaxZ() > container.getHeight())
			return false;
		return true;

	}

	public String print2File(String filename) {
		String txt = "";
		try {
			// Create file
			FileWriter fstream = new FileWriter(filename, false);
			BufferedWriter out = new BufferedWriter(fstream);
			out.write(container.getWidth() + "\n");
			out.write(container.getLength() + "\n");
			out.write(container.getHeight() + "\n");

			List<PositionSimpleBlockPair> positionBlockList = new ObjectArrayList<PositionSimpleBlockPair>();
			for (EmptySpaceBlockPair emptySpaceBlockPair : packedBlockList) {
				positionBlockList.add(emptySpaceBlockPair.getPairMainBlock());
				if (!emptySpaceBlockPair.getBlock().isSimpleBlock())
					positionBlockList.add(emptySpaceBlockPair
							.getPairNextBlock());

			}
			for (PositionSimpleBlockPair positionBlockPair : positionBlockList) {
				SimpleBlock simpleBlock = positionBlockPair.getSimpleblock();
				Point position = positionBlockPair.getPosition();
				BoxPack b = simpleBlock.getBox();
				for (int j = 1; j <= simpleBlock.getyBoxNumber(); j++) {
					for (int i = 1; i <= simpleBlock.getzBoxNumber(); i++) {
						for (int k = 1; k <= simpleBlock.getxBoxNumber(); k++) {

							Point p = new Point(position.getX() + b.getWidth()
									* (k - 1), position.getY() + b.getLength()
									* (j - 1), position.getZ() + b.getHeight()
									* (i - 1));

							out.write(b.getWidth() + " " + b.getLength() + " "
									+ b.getHeight() + " " + p.getX() + " "
									+ p.getY() + " " + p.getZ() + " "
									+ b.getId() + "\n");

						}

					}

				}

			}
			txt = out.toString();
			out.close();
		} catch (Exception e) {// Catch exception if any
			System.err.println("Error: " + e.getMessage());
		}

		return txt;

	}

	public List<EmptySpaceBlockPair> getPackedBlockList() {
		return packedBlockList;
	}

	public void setPackedBlockList(List<EmptySpaceBlockPair> packedBlockList) {
		this.packedBlockList = packedBlockList;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public Container getContainer() {
		return container;
	}

	public void setContainer(Container container) {
		this.container = container;
	}

	public List<BoxInput> getBoxInputList() {
		return boxInputList;
	}

	public void setBoxInputList(List<BoxInput> boxInputList) {
		this.boxInputList = boxInputList;
	}

	public ObjectSortedSet<BoxPack> getBoxPackSet() {
		return boxPackSet;
	}

	public void setBoxPackSet(ObjectSortedSet<BoxPack> boxPackSet) {
		this.boxPackSet = boxPackSet;
	}

	public ObjectSortedSet<XYRectangle> getXYRectanglePositiveSet() {
		return xyRectanglePositiveSet;
	}

	public void setXYRectanglePositiveSet(
			ObjectRBTreeSet<XYRectangle> xyRectanglePositiveSet) {
		this.xyRectanglePositiveSet = xyRectanglePositiveSet;
	}

	public ObjectRBTreeSet<XYRectangle> getXYRectangleNegativeSet() {
		return xyRectangleNegativeSet;
	}

	public void setXYRectangleNegativeSet(
			ObjectRBTreeSet<XYRectangle> xyRectangleNegativeSet) {
		this.xyRectangleNegativeSet = xyRectangleNegativeSet;
	}

	public ObjectRBTreeSet<XZRectangle> getXZRectanglePositiveSet() {
		return xzRectanglePositiveSet;
	}

	public void setXZRectanglePositiveSet(
			ObjectRBTreeSet<XZRectangle> xzRectanglePositiveSet) {
		this.xzRectanglePositiveSet = xzRectanglePositiveSet;
	}

	public ObjectRBTreeSet<XZRectangle> getXZRectangleNegativeSet() {
		return xzRectangleNegativeSet;
	}

	public void setXZRectangleNegativeSet(
			ObjectRBTreeSet<XZRectangle> xzRectangleNegativeSet) {
		this.xzRectangleNegativeSet = xzRectangleNegativeSet;
	}

	public ObjectRBTreeSet<YZRectangle> getYZRectanglePositiveSet() {
		return yzRectanglePositiveSet;
	}

	public void setYZRectanglePositiveSet(
			ObjectRBTreeSet<YZRectangle> yzRectanglePositiveSet) {
		this.yzRectanglePositiveSet = yzRectanglePositiveSet;
	}

	public ObjectRBTreeSet<YZRectangle> getYZRectangleNegativeSet() {
		return yzRectangleNegativeSet;
	}

	public void setYZRectangleNegativeSet(
			ObjectRBTreeSet<YZRectangle> yzRectangleNegativeSet) {
		this.yzRectangleNegativeSet = yzRectangleNegativeSet;
	}

	public void updateXYZMin() {
		for (BoxPack boxpack : boxPackSet) {
			if (getQuantity(boxpack) > 0) {
				if (xMinSize > boxpack.getWidth())
					xMinSize = boxpack.getWidth();
				if (yMinSize > boxpack.getLength())
					yMinSize = boxpack.getLength();
				if (yMinSize > boxpack.getHeight())
					zMinSize = boxpack.getHeight();

			}

		}

	}

	public double getEvaluationScore() {
		return evaluationScore;
	}

	public boolean isValid() {
		return isValid;
	}

	public int getxMinSize() {
		return xMinSize;
	}

	public int getyMinSize() {
		return yMinSize;
	}

	public int getzMinSize() {
		return zMinSize;
	}

	public void validate() {
		// TODO: need to implement validate method
	}

	public boolean noBoxLeft() {
		for (BoxInput boxInput : boxInputList) {
			if (boxInput.getQuantity() > 0)
				return false;
		}
		return true;
	}

	public String evaluate() {
		int volume = 0;
		volume = getTotalVolume();
		evaluationScore = (double) volume / (double) container.getVolume();
		return "\n::: อัตราส่วนการใช้พื้นที่ : " + evaluationScore
				+ " ( Sum Volume Of Block(" + (double) volume
				+ ") / Volume Of Container(" + (double) container.getVolume()
				+ ") ) :::";
	}

	public void sortByPressure(boolean check) {
		if (check) {
			for (int i = 0; i < packedBlockList.size(); i++) {
				EmptySpaceBlockPair current = packedBlockList.get(i);
				int base = current.getBlock().getPressure();
				if (base == 0) {
					continue;
				}
				int totalWeight = 0;
				for (int j = 0; j < packedBlockList.size(); j++) {
					if (!isValidPosition(current.getPosition(), packedBlockList
							.get(j).getPosition(), true)) {
						totalWeight += packedBlockList.get(j).getBlock()
								.getWeight();
					}
					if (current.getBlock().getNextBlock() != null) {
						current.getBlock().setNextBlock(
								pressureNextBox(current.getBlock()
										.getNextBlock()));
					}
				}
				totalWeight += getAllWeight(current);
				if (base < totalWeight) {
					packedBlockList.set(i, minusBelowBox(current));
					sortByPressure(true);
				}
			}
			sortByPressure(false);
		}
	}

	public SimpleBlock pressureNextBox(SimpleBlock block) {
		int base = block.getBox().getPressure();
		while (true) {
			int totalWeight = 0;
			for (int i = 0; i < block.getzBoxNumber(); i++) {
				totalWeight += block.getBox().getWeight();
			}
			if (base < totalWeight) {
				block.zBoxNumber--;
			} else {
				break;
			}
		}
		return block;
	}

	public boolean isFloat() {
		List<PositionSimpleBlockPair> positionBlockList = new ObjectArrayList<PositionSimpleBlockPair>();
		for (EmptySpaceBlockPair emptySpaceBlockPair : packedBlockList) {
			positionBlockList.add(emptySpaceBlockPair.getPairMainBlock());
			if (!emptySpaceBlockPair.getBlock().isSimpleBlock())
				positionBlockList.add(emptySpaceBlockPair.getPairNextBlock());

		}
		for (PositionSimpleBlockPair positionSimple1 : positionBlockList) {
			for (PositionSimpleBlockPair positionSimple2 : positionBlockList) {
				if (!isValidPosition(positionSimple1.getPosition(),
						positionSimple2.getPosition(), false)) {
					return false;
				}
			}
		}
		return true;
	}

	public boolean isOutline() {
		List<PositionSimpleBlockPair> positionBlockList = new ObjectArrayList<PositionSimpleBlockPair>();
		for (EmptySpaceBlockPair emptySpaceBlockPair : packedBlockList) {
			positionBlockList.add(emptySpaceBlockPair.getPairMainBlock());
			if (!emptySpaceBlockPair.getBlock().isSimpleBlock())
				positionBlockList.add(emptySpaceBlockPair.getPairNextBlock());

		}
		for (PositionSimpleBlockPair positionBlockPair : positionBlockList) {
			SimpleBlock simpleBlock = positionBlockPair.getSimpleblock();
			Point position = positionBlockPair.getPosition();
			BoxPack b = simpleBlock.getBox();
			for (int j = 1; j <= simpleBlock.getyBoxNumber(); j++) {
				for (int i = 1; i <= simpleBlock.getzBoxNumber(); i++) {
					for (int k = 1; k <= simpleBlock.getxBoxNumber(); k++) {

						Point p = new Point(position.getX() + b.getWidth()
								* (k - 1), position.getY() + b.getLength()
								* (j - 1), position.getZ() + b.getHeight()
								* (i - 1));
						if (p.getZ() + b.getHeight() > container.getHeight()) {
							return false;
						}
					}

				}

			}

		}
		return true;
	}

	public int getTotalVolume() {
		int volume = 0;
		List<PositionSimpleBlockPair> positionBlockList = new ObjectArrayList<PositionSimpleBlockPair>();
		for (EmptySpaceBlockPair emptySpaceBlockPair : packedBlockList) {
			positionBlockList.add(emptySpaceBlockPair.getPairMainBlock());
			if (!emptySpaceBlockPair.getBlock().isSimpleBlock())
				positionBlockList.add(emptySpaceBlockPair.getPairNextBlock());

		}
		for (PositionSimpleBlockPair positionBlockPair : positionBlockList) {
			SimpleBlock simpleBlock = positionBlockPair.getSimpleblock();
			BoxPack b = simpleBlock.getBox();
			for (int j = 1; j <= simpleBlock.getyBoxNumber(); j++) {
				for (int i = 1; i <= simpleBlock.getzBoxNumber(); i++) {
					for (int k = 1; k <= simpleBlock.getxBoxNumber(); k++) {
						volume += b.getVolume();
					}

				}

			}

		}
		return volume;
	}

	public EmptySpaceBlockPair minusBelowBox(EmptySpaceBlockPair emp) {
		emp.getBlock().getMainBlock().zBoxNumber--;
		return emp;
	}

	public boolean isValidPosition(Point p1, Point p2, boolean chk) {
		int positionX1 = p1.getX();
		int positionY1 = p1.getY();
		int positionZ1 = p1.getZ();
		int positionX2 = p2.getX();
		int positionY2 = p2.getY();
		int positionZ2 = p2.getZ();
		if (chk) {
			if (positionX1 == positionX2 || positionY1 == positionY2) {
				if (positionZ1 < positionZ2) {
					return false;
				}
			}
		} else {
			if (positionZ1 < positionZ2 || positionZ1 > positionZ2) {
				if (positionX1 == positionX2 && positionY1 == positionY2) {
					return true;
				} else {
					return false;
				}
			}

		}

		return true;
	}

	public int getAllWeight(EmptySpaceBlockPair space) {
		return (space.getBlock().getMainBlock().getzBoxNumber() * space
				.getBlock().getMainBlock().getBox().getWeight())
				- space.getBlock().getMainBlock().getBox().getWeight();
	}

	public boolean isValidBlock(Block block) {
		if (this.getQuantity(block.getMainBlock().getBox()) >= block
				.getMainBlock().getBoxNumber()) {
			if (!block.isSimpleBlock()) {
				if (this.getQuantity(block.getNextBlock().getBox()) >= block
						.getNextBlock().getBoxNumber()) {
					return true;
				} else {
					return false;
				}

			} else {
				return true;
			}
		} else {
			return false;
		}

	}

	public boolean checkFloor() {
		List<PositionSimpleBlockPair> positionBlockList = new ObjectArrayList<PositionSimpleBlockPair>();
		for (EmptySpaceBlockPair emptySpaceBlockPair : packedBlockList) {
			positionBlockList.add(emptySpaceBlockPair.getPairMainBlock());
			if (!emptySpaceBlockPair.getBlock().isSimpleBlock())
				positionBlockList.add(emptySpaceBlockPair.getPairNextBlock());

		}
		for (PositionSimpleBlockPair positionBlockPair : positionBlockList) {
			SimpleBlock simpleBlock = positionBlockPair.getSimpleblock();
			Point position = positionBlockPair.getPosition();
			BoxPack b = simpleBlock.getBox();
			for (int j = 1; j <= simpleBlock.getyBoxNumber(); j++) {
				for (int i = 1; i <= simpleBlock.getzBoxNumber(); i++) {
					for (int k = 1; k <= simpleBlock.getxBoxNumber(); k++) {

						Point p = new Point(position.getX() + b.getWidth()
								* (k - 1), position.getY() + b.getLength()
								* (j - 1), position.getZ() + b.getHeight()
								* (i - 1));
						if (p.getZ() == 0) {
							System.out.println(getBigBoxVolume() + " / "
									+ b.getVolume());
							if (getBigBoxVolume() != b.getVolume()) {
								return false;
							}
						}
					}

				}

			}

		}
		return true;
	}

	public int getBigBoxVolume() {
		int max = 0;
		for (EmptySpaceBlockPair emp : packedBlockList) {
			if (emp.getBlock().getMainBlock().getBox().getVolume() > max) {
				max = emp.getBlock().getMainBlock().getBox().getVolume();
			}
			if (emp.getBlock().getNextBlock() != null) {
				if (emp.getBlock().getNextBlock().getBox().getVolume() > max) {
					max = emp.getBlock().getNextBlock().getBox().getVolume();
				}
			}

		}
		return max;
	}

	public int getTotalWeight() {
		int weight = 0;
		List<PositionSimpleBlockPair> positionBlockList = new ObjectArrayList<PositionSimpleBlockPair>();
		for (EmptySpaceBlockPair emptySpaceBlockPair : packedBlockList) {
			positionBlockList.add(emptySpaceBlockPair.getPairMainBlock());
			if (!emptySpaceBlockPair.getBlock().isSimpleBlock())
				positionBlockList.add(emptySpaceBlockPair.getPairNextBlock());

		}
		for (PositionSimpleBlockPair positionBlockPair : positionBlockList) {
			SimpleBlock simpleBlock = positionBlockPair.getSimpleblock();
			BoxPack b = simpleBlock.getBox();
			for (int j = 1; j <= simpleBlock.getyBoxNumber(); j++) {
				for (int i = 1; i <= simpleBlock.getzBoxNumber(); i++) {
					for (int k = 1; k <= simpleBlock.getxBoxNumber(); k++) {
						weight += b.getWeight();
					}
				}
			}
		}
		return weight;
	}
	
	public State getStateResult(State bestState) {
		List<PositionSimpleBlockPair> positionBlockList = new ObjectArrayList<PositionSimpleBlockPair>();
		for (EmptySpaceBlockPair emptySpaceBlockPair : bestState.getPackedBlockList()) {
			positionBlockList.add(emptySpaceBlockPair.getPairMainBlock());
			if (!emptySpaceBlockPair.getBlock().isSimpleBlock())
				positionBlockList.add(emptySpaceBlockPair.getPairNextBlock());

		}
		for (PositionSimpleBlockPair positionBlockPair : positionBlockList) {
			SimpleBlock simpleBlock = positionBlockPair.getSimpleblock();
			BoxPack b = simpleBlock.getBox();
			for(int i = 0 ; i < boxInputList.size() ; i++){
				if(b.getId() == boxInputList.get(i).getId()){
					int quantity = boxInputList.get(i).getQuantity() - simpleBlock.getBoxNumber() ;
					if(quantity > 0){
						boxInputList.get(i).setQuantity(quantity);
					}else{
						boxInputList.get(i).setQuantity(0);
					}
				}	
			}			
		}
		return this ;
	}
	
	public boolean isEmpty(){
		int sum = 0 ;
		for (BoxInput b : boxInputList) {
			sum += b.getQuantity() ;
			if(sum > 0){
				return false ;
			}
		}
		return true;
	}
}
